// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------

#pragma once

namespace Reliability
{
    namespace ReconfigurationAgentComponent
    {
        namespace Infrastructure
        {
            // The retry state provides the following infrastructure around managing retries:
            // 1. Sequencing of retries: The retry state handles sequencing of retries
            //    Each round of retries has its own sequence number so a retry for an 
            //    earlier sequence number does not change the retry state for the current
            //    round of retries
            //    
            // 2. Throttling of retries using a retry policy
            // 
            class RetryState
            {
            public:
                RetryState(IRetryPolicySPtr const & retryPolicy) :
                retryPolicy_(retryPolicy),
                sequenceNumber_(0),
                isPending_(false)
                {
                }

                __declspec(property(get = get_IsPending)) bool IsPending;
                bool get_IsPending() const { return isPending_; }
                void Test_SetIsPending(bool value) { isPending_ = value; }

                // A new round of retries is starting
                // It is not required for the previous round to complete
                void Start()
                {
                    isPending_ = true;
                    retryPolicy_->Clear();
                    sequenceNumber_++;
                }

                // The current round of retry has completed
                void Finish()
                {
                    isPending_ = false;
                }

                // Returns whether retry should take place at 'now'
                // If returns true then it returns the sequence number for this retry
                bool ShouldRetry(
                    Common::StopwatchTime now,
                    __out int64 & sequenceNumber) const
                {
                    sequenceNumber = 0;

                    if (!isPending_)
                    {
                        return false;
                    }

                    if (!retryPolicy_->ShouldRetry(now))
                    {
                        return false;
                    }
                    
                    sequenceNumber = sequenceNumber_;
                    return true;
                }

                // Inform the retry state that a retry for 'sequenceNumber' happened at 'now'
                // It is possible in the meantime a new incarnation of retry happened
                // Returns whether any update happened to this object or not
                bool OnRetry(int64 sequenceNumber, Common::StopwatchTime now)
                {
                    // Since sequence numbers are generated by this object and exposed by ShouldRetry
                    // it can never happen that a caller knows a higher sequence number
                    TESTASSERT_IF(sequenceNumber > sequenceNumber_, "Unexpected incoming sequence number {0} for retry state greater than current {1}", sequenceNumber, sequenceNumber_);

                    if (!isPending_)
                    {
                        return false;
                    }

                    if (sequenceNumber != sequenceNumber_)
                    {
                        return false;
                    }

                    retryPolicy_->OnRetry(now);

                    return true;
                }

            private:

                bool isPending_;
                int64 sequenceNumber_;
                IRetryPolicySPtr retryPolicy_;
            };
        }
    }
}



